from .package_deps import *
from .formatp import *
from .package_files import *
from utils.checksum import *
import aptly
from .deb import *
from . import remote
from .package_collection import *
from files.package_pool import *
from files.public import *
class Package(object):
    Name=''
    Version=''
    Architecture=''
    SourceArchitecture=''
    Source=''
    Provides=[]
    FilesHash=0
    IsInstaller=False
    IsSource=False
    IsUdeb=False
    V06Plus=False
    deps:PackageDependencies=None
    extra:Stanza=None
    files   :PackageFiles =None
    contents=[]
    def __getstate__(self):
        state=self.__dict__.copy()
        state['Provides']=self.Provides
        return state
    # // Mother collection
    collection :PackageCollection=None
    def Key(self,prefix :str):
        if self.V06Plus:
            return ("%sP%s %s %s %08x"%( prefix, self.Architecture, self.Name, self.Version, self.FilesHash))
    
        return self.ShortKey(prefix)
    

    # // ShortKey returns key for the package that should be unique in one list
    def ShortKey(self,prefix :str) :
        return ("%sP%s %s %s"% (prefix, self.Architecture, self.Name, self.Version))


    # // String creates readable representation
    def  String(self)->str:
        return "%s_%s_%s".format( self.Name, self.Version, self.Architecture)


    # // ExtendedStanza returns package stanza enhanced with aptly-specific fields
    def ExtendedStanza(self):
        stanza = self.Stanza()
        stanza.data["FilesHash"] = ("%08x"%self.FilesHash)
        stanza.data["Key"] = str(self.Key(""))
        stanza.data["ShortKey"] = str(self.ShortKey(""))

        return stanza


    # // MarshalJSON implements json.Marshaller interface
    def  MarshalJSON(self) :
        return self.ExtendedStanza()


    # // GetField returns fields from package
    def GetField(self,name :str) ->str:
        
        # // $Version is handled in FieldQuery
        if name== "$Source":
            if self.IsSource :
                return ""
            
            source = self.Source
            if source == "" :
                return self.Name
            elif source.find( "(") != -1 :
                pos =source.find( "(")
                return source[:pos].strip()
            
            return source
        elif name== "$SourceVersion":
            if self.IsSource :
                return ""
            
            source = self.Source
            pos = source.find( "(")
            if  pos != -1 :
                pos2=source.rfind(")")
                if  pos2 != -1 and pos2 > pos :
                    return source[pos+1 : pos2].strip()
                
            
            return self.Version
        elif name== "$Architecture":
            return self.Architecture
        elif name== "$PackageType":
            if self.IsSource :
                return PackageTypeSource
            
            if self.IsUdeb :
                return PackageTypeUdeb
            return PackageTypeBinary
        elif name== "Name":
            return self.Name
        elif name== "Version":
            return self.Version
        elif name== "Architecture":
            if self.IsSource :
                return self.SourceArchitecture
            
            return self.Architecture
        elif name== "Source":
            return self.Source
        elif name== "Depends":
            return ", ".join(self.Deps().Depends)
        elif name== "Pre-Depends":
            return ", ".join(self.Deps().PreDepends)
        elif name== "Suggests":
            return ", ".join(self.Deps().Suggests )
        elif name== "Recommends":
            return ", ".join(self.Deps().Recommends)
        elif name== "Provides":
            return ", ".join(self.Provides)
        elif name== "Build-Depends":
            return ", ".join(self.Deps().BuildDepends)
        elif name== "Build-Depends-Indep":
            return ", ".join(self.Deps().BuildDependsInDep)
        else:
            return self.Extra()[name]

    # // MatchesArchitecture checks whether packages matches specified architecture
    def MatchesArchitecture(self,arch ):
        if self.Architecture == ArchitectureAll and arch != ArchitectureSource :
            return True
        
        return self.Architecture == arch
    # // Extra returns Stanza of extra fields (it may load it from collection)
    def Extra(self) :
        if self.extra is None  :
            if self.collection is None  :
                raise Exception("extra is None  and collection is None ")
            
            self.extra = self.collection.loadExtra(self)
        

        return self.extra

    # // Stanza creates original stanza from package
    def  Stanza(self):
        result = self.Extra().Copy()
        result["Package"] = self.Name
        result["Version"] = self.Version

        if self.IsSource :
            result["Architecture"] = self.SourceArchitecture
        else :
            result["Architecture"] = self.Architecture
            if self.Source != "" :
                result["Source"] = self.Source
            
        

        if self.IsSource:
            md5=[]
            sha1=[]
            sha256=[]
            sha512 =[]

            for f in self.Files() :
                if f.Checksums.MD5 != "" :
                    md5 = md5.append(" {} {} {}\n".format (f.Checksums.MD5, f.Checksums.Size, f.Filename))
                
                if f.Checksums.SHA1 != "" :
                    sha1 = sha1.append(" {} {} {}\n".format(f.Checksums.SHA1, f.Checksums.Size, f.Filename))
                
                if f.Checksums.SHA256 != "" :
                    sha256 = sha256.append(" {} {} {}\n".format (f.Checksums.SHA256, f.Checksums.Size, f.Filename))
                
                if f.Checksums.SHA512 != "" :
                    sha512 = sha512.append(" {} {} {}\n".format( f.Checksums.SHA512, f.Checksums.Size, f.Filename))
                
            

            result["Files"] = "".join(md5 )
            if len(sha1) > 0 :
                result["Checksums-Sha1"] = "".join(sha1)
            
            if len(sha256) > 0 :
                result["Checksums-Sha256"] = "".join(sha256)
            
            if len(sha512) > 0 :
                result["Checksums-Sha512"] = "".join(sha512)
            
        elif self.IsInstaller :
            sha256 = []
            for  f in self.Files() :
                sha256 = sha256.append("{}  {}".format( f.Checksums.SHA256, f.Filename))
            
            result[""] = "\n".join(sha256)
        else :
            f = self.Files()[0]
            result["Filename"] = f.DownloadURL()
            if f.Checksums.MD5 != "" :
                result["MD5sum"] = f.Checksums.MD5
            
            if f.Checksums.SHA1 != "" :
                result["SHA1"] = f.Checksums.SHA1
            
            if f.Checksums.SHA256 != "" :
                result["SHA256"] = f.Checksums.SHA256
            
            if f.Checksums.SHA512 != "" :
                result["SHA512"] = f.Checksums.SHA512
            
            result["Size"] = "{}".format( f.Checksums.Size)
        

        deps = self.Deps()
# TODO 此处需要注意，在写packages的时候容易出问题
        if len(deps.Depends)  :
            result["Depends"] =", ".join(deps.Depends )
        
        if len(deps.PreDepends ):
            result["Pre-Depends"] = ", ".join(deps.PreDepends)
        
        if len(deps.Suggests)   :
            result["Suggests"] = ", ".join(deps.Suggests)
        
        if len(deps.Recommends) :
            result["Recommends"] = ", ".join(deps.Recommends)
        
        if len(self.Provides)  :
            result["Provides"] = ", ".join(self.Provides)
        
        if len(deps.BuildDepends) :
            result["Build-Depends"] = ", ".join(deps.BuildDepends)
        
        if len(deps.BuildDependsInDep)  :
            result["Build-Depends-Indep"] = ", ".join(deps.BuildDependsInDep)

        return result

    # // Files returns parsed files records (it may load it from collection)
    def  Files(self)->PackageFiles :
        if self.files is None  :
            if self.collection is None :
                raise Exception("files is None  and collection is None ")
            

            self.files = self.collection.loadFiles(self)

        return self.files
    def  UpdateFiles(self,files ) :
        self.files = files
        self.FilesHash = files.Hash()



    # // Equals compares two packages to be identical
    def Equals(self,p2 ) :
        return self.Name == p2.Name and self.Version == p2.Version and self.SourceArchitecture == p2.SourceArchitecture and \
            self.Architecture == p2.Architecture and self.Source == p2.Source and self.IsSource == p2.IsSource and \
            self.FilesHash == p2.FilesHash

     # // CalculateContents looks up contents in package file
    def CalculateContents(self,packagePool, progress ):
        if self.IsSource :
            return None, None
        

        file = self.Files()[0]
        poolPath= file.GetPoolPath(packagePool)
        # if err is not None  {
        #     if progress is not None  {
        #         progress.ColoredPrintf("@y[!]@| @!Failed to build pool path: @| %s", err)
        #     }
        #     return None, err
        # }

        reader= packagePool.Open(poolPath)
        # if err is not None  {
        #     if progress is not None  {
        #         progress.ColoredPrintf("@y[!]@| @!Failed to open package in pool: @| %s", err)
        #     }
        #     return None, err
        # }
        # defer reader.Close()
        contents= GetContentsFromDeb(reader)
        # if err is not None  {
        #     if progress is not None  {
        #         progress.ColoredPrintf("@y[!]@| @!Failed to generate package contents: @| %s", err)
        #     }
        #     return None, err
        # }

        return contents, None
    # // GetName returns package name
    def GetName(p) :
        return p.Name


    # // GetVersion returns package version
    def GetVersion(p) :
        return p.Version


    # // GetArchitecture returns package arch
    def GetArchitecture(p):
        return p.Architecture
    # // QualifiedName returns [$SECTION/]$NAME
    def QualifiedName(p) :
        section = p.Extra()["Section"]
        if section != "" :
            return section + "/" + p.Name
        return p.Name

    # // Deps returns parsed package dependencies (it may load it from collection)
    def Deps(p) ->PackageDependencies:
        if p.deps is None  :
            if p.collection is None  :
                raise Exception("deps is None  and collection is None ")
            p.deps = p.collection.loadDependencies(p)
        

        return p.deps
    






    # // Contents returns cached package contents
    def  Contents(p,packagePool :PackagePool, progress ):
        if p.IsSource :
            return None
        

        return p.collection.loadContents(p, packagePool, progress)
    




    # // LinkFromPool links package file from pool to dist's pool location
    def LinkFromPool(p,publishedStorage :PublishedStorage, packagePool :PackagePool,
        prefix, relPath :str, force :bool) :
        index=0
        for f in p.Files() :
            
            sourcePoolPath = f.GetPoolPath(packagePool)



            err = publishedStorage.LinkFromPool(prefix, relPath, f.Filename, packagePool, sourcePoolPath, f.Checksums, force)


            if p.IsSource :
                p.Extra().data["Directory"] = relPath
            else :
                p.Files()[index].downloadPath = relPath
            
            index+=1

 

    # // PoolDirectory returns directory in package pool of published repository for this package files
    def PoolDirectory(p) :
        source = p.Source
        if source == "" :
            source = p.Name
        elif source.find(source, "(")!=-1:
            pos=source.find(source, "(")
            source = source[:pos].strip()

        if len(source) < 2 :
            print("package source %s too short", source)
            return None,"package source {} too short".format(source)
        


        if source.startswith( "lib") :
            subdir = source[:4]
        else :
            subdir = source[:1]


        return os.path.join(subdir, source),None


    #   DownloadList returns list of missing package files for download in format
    #  
    def DownloadList(p,packagePool , checksumStorage ):
        result =[]

        files = p.Files()
        for idx in  files :
            verified= files[idx].Verify(packagePool, checksumStorage)

            if not verified :
                result = result.append( PackageDownloadTask=files[idx])
            

        return result

    # // VerifyFiles verifies that all package files have neen correctly downloaded
    def  VerifyFiles(p,packagePool, checksumStorage ) :
        result = True

        for  f in p.Files() :
            result = f.Verify(packagePool, checksumStorage)
            if not result :
                return
        return


    # // FilepathList returns list of paths to files in package repository
    def  FilepathList(p,packagePool ):
        result =[]

        for f in p.Files() :
            result.append( f.GetPoolPath(packagePool))
            
        return result

   
    



PackageTypeBinary    = "deb"
PackageTypeUdeb      = "udeb"
PackageTypeSource    = "source"
PackageTypeInstaller = "installer"

ArchitectureAll    = "all"
ArhictectureAny    = "any"
ArchitectureSource = "source"


# // NewPackageFromControlFile creates Package from parsed Debian control file
def NewPackageFromControlFile(inputStanza :Stanza):
    result=Package()
    result.Name=  inputStanza.get("Package")
    result.Version=inputStanza.get("Version")
    result.Architecture= inputStanza.get("Architecture")
    result.Source=inputStanza.get("Source",result.Source) 
    result.V06Plus=  True
    if result.Name is not None:
        inputStanza.pop( "Package")
    if result.Version is not None:
        inputStanza.pop("Version")
    if result.Architecture is not None:
        inputStanza.pop( "Architecture")
    if len(result.Source) :
        inputStanza.pop("Source")

    filesize= int(inputStanza.get("Size",'0'))

    md5= inputStanza.get("MD5sum",'') 
    
    if len(md5)==0:
        # // there are some broken repos out there with MD5 in wrong field
        md5 = inputStanza.get("MD5Sum",'').strip()
    
    checkSumInfo=ChecksumInfo()
    checkSumInfo.Size= filesize
    checkSumInfo.MD5= md5
    checkSumInfo.SHA1= inputStanza.get("SHA1",'') .strip()
    
    checkSumInfo.SHA256= inputStanza.get("SHA256",'') .strip()
    
    checkSumInfo.SHA512= inputStanza.get("SHA512",'').strip()
    

    
    newPackageFile=PackageFile()
    
    newPackageFile.Filename= os.path.basename(inputStanza.get("Filename",'') )
    newPackageFile.downloadPath=os.path.dirname(inputStanza.get("Filename",'') )
    newPackageFile.Checksums=checkSumInfo
    newPackageFiles=PackageFiles()
    newPackageFiles.append(newPackageFile)
    result.UpdateFiles(newPackageFiles)
    if inputStanza.get("Filename") is not None:
        inputStanza.pop( "Filename")
    if inputStanza.get("MD5sum") is not None:
        inputStanza.pop( "MD5sum")
    if inputStanza.get("MD5Sum") is not None:
        inputStanza.pop( "MD5Sum")
    if inputStanza.get("SHA1") is not None:
        inputStanza.pop( "SHA1")
    if inputStanza.get("SHA256") is not None:
        inputStanza.pop( "SHA256")
    if inputStanza.get("SHA512") is not None:
        inputStanza.pop( "SHA512")
    if inputStanza.get("Size") is not None:
        inputStanza.pop( "Size")

    depends = PackageDependencies()
    depends.Depends = parseDependencies(inputStanza, "Depends") or depends.PreDepends
    depends.PreDepends = parseDependencies(inputStanza, "Pre-Depends") or depends.PreDepends
    depends.Suggests = parseDependencies(inputStanza, "Suggests") or depends.Suggests
    depends.Recommends = parseDependencies(inputStanza, "Recommends") or depends.Recommends
    result.deps = depends
    result.Provides = parseDependencies(inputStanza, "Provides") or result.Provides

    result.extra = inputStanza

    return result


# // NewSourcePackageFromControlFile creates Package from parsed Debian control file for source package
def NewSourcePackageFromControlFile(inputStanza:Stanza):
    result=Package()
    result.Name=  inputStanza["Package"]
    result.Version=     inputStanza["Version"]
    result.Architecture= "source"
    result.SourceArchitecture=inputStanza["Architecture"]
    result.Source=      inputStanza["Source"]
    result.V06Plus=  True
    result.IsSource=True
    inputStanza.pop( "Package")
    inputStanza.pop("Version")
    inputStanza.pop( "Architecture")

    files = PackageFiles()
    files = files.ParseSumFields(inputStanza)


    inputStanza.pop( "Files")
    inputStanza.pop( "Checksums-Sha1")
    inputStanza.pop( "Checksums-Sha256")

    for i in range(len(files)) :
        files[i].downloadPath = inputStanza["Directory"]
    

    result.UpdateFiles(files)

    depends = PackageDependencies()
    depends.BuildDepends = parseDependencies(inputStanza, "Build-Depends")
    depends.BuildDependsInDep = parseDependencies(inputStanza, "Build-Depends-Indep")
    result.deps = depends

    result.extra = inputStanza

    return result


# // NewUdebPackageFromControlFile creates .udeb Package from parsed Debian control file
def NewUdebPackageFromControlFile(inputStanza:Stanza) :
    p = NewPackageFromControlFile(inputStanza)
    p.IsUdeb =True
    return p


# // NewInstallerPackageFromControlFile creates a dummy installer Package from parsed hash sum file
def NewInstallerPackageFromControlFile(inputStanza:Stanza , repo :remote.RemoteRepo, component, architecture :str, d ):
    p = Package()
    # import remote
    p.Name="installer"
    p.Architecture= architecture
    p.IsInstaller=True
    p.V06Plus=True
    p.extra={}
    p.deps=PackageDependencies()
    

    files =PackageFiles()
    files=files.ParseSumField(inputStanza[""], lambda sum,data:setattr(sum,'MD5',data), False, False)


    # var relPath string
    if repo.Distribution == aptly.DistributionFocal :
        relPath = os.path.join("dists", repo.Distribution, component, "{}-{}".format(p.Name, architecture), "current", "legacy-images")
    else :
        relPath = os.path.join("dists", repo.Distribution, component, "{}-{}".format(p.Name, architecture), "current", "images")
    
    for i in range(len(files)) :
        files[i].downloadPath = relPath

        url = remote.PackageURL(repo,files[i].DownloadURL())
        # 通过请求获取具体的字节数
        size  = d.GetLength(gocontext.TODO(), url)

        files[i].Checksums.Size = size
        raise

    p.UpdateFiles(files)
    return p, None

# // Key returns unique key identifying package



    # // MatchesDependency checks whether package matches specified dependency
    # def (p *Package) MatchesDependency(dep Dependency) bool {
    #     if dep.Architecture != "" and !p.MatchesArchitecture(dep.Architecture) {
    #         return false
    #     }

    #     if dep.Relation == VersionDontCare {
    #         if utils.StrSliceHasItem(p.Provides, dep.Pkg) {
    #             return true
    #         }
    #         return dep.Pkg == p.Name
    #     }

    #     if dep.Pkg != p.Name {
    #         return false
    #     }

    #     r := CompareVersions(p.Version, dep.Version)

    #     switch dep.Relation {
    #     case VersionEqual:
    #         return r == 0
    #     case VersionLess:
    #         return r < 0
    #     case VersionGreater:
    #         return r > 0
    #     case VersionLessOrEqual:
    #         return r <= 0
    #     case VersionGreaterOrEqual:
    #         return r >= 0
    #     case VersionPatternMatch:
    #         matched, err := filepath.Match(dep.Version, p.Version)
    #         return err is None  and matched
    #     case VersionRegexp:
    #         return dep.Regexp.FindStringIndex(p.Version) is not None 
    #     }

    #     raise ("unknown relation")
    # }



    # // GetDependencies compiles list of dependncies by flags from options
    # def (p *Package) GetDependencies(options int) (dependencies []string) {
    #     deps := p.Deps()

    #     dependencies = make([]string, 0, 30)
    #     dependencies = append(dependencies, deps.Depends...)
    #     dependencies = append(dependencies, deps.PreDepends...)

    #     if options&DepFollowRecommends == DepFollowRecommends {
    #         dependencies = append(dependencies, deps.Recommends...)
    #     }

    #     if options&DepFollowSuggests == DepFollowSuggests {
    #         dependencies = append(dependencies, deps.Suggests...)
    #     }

    #     if options&DepFollowBuild == DepFollowBuild {
    #         dependencies = append(dependencies, deps.BuildDepends...)
    #         dependencies = append(dependencies, deps.BuildDependsInDep...)
    #     }

    #     if options&DepFollowSource == DepFollowSource {
    #         source := p.Source
    #         if source == "" {
    #             source = p.Name
    #         }
    #         if strings.Contains(source, ")") {
    #             dependencies = append(dependencies, fmt.Sprintf("%s {source}", source))
    #         } else {
    #             dependencies = append(dependencies, fmt.Sprintf("%s (= %s) {source}", source, p.Version))
    #         }
    #     }

    #     return
    # }







# // PackageDownloadTask is a element of download queue for the package
class PackageDownloadTask :
    File=Package
    Additional=[]
    TempDownPath=''
    Done =None


